﻿using System;
using System.ComponentModel;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;

namespace Microsoft.WowAddonStudio.FrameXml.Serialization.TypeConverters
{
	/// <summary>
	/// Provides a unified way of converting types of values to other types,
	/// as well as for accessing standard values and subproperties.
	/// </summary>
	public class DimensionTypeConverter : TypeConverter
	{
		private static readonly Regex dimensionRegex =
			new Regex(@"(([-+]?\d+)?;([-+]?\d+)?)?((Rel|Abs)([-+]?\d+(.\d+)?);([-+]?\d+(.\d+)?))?", RegexOptions.Compiled);

		/// <summary>
		/// Returns whether this converter can convert the object to the specified type, using the specified context.
		/// </summary>
		/// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param>
		/// <param name="destinationType">A <see cref="T:System.Type"/> that represents the type you want to convert to.</param>
		/// <returns>
		/// true if this converter can perform the conversion; otherwise, false.
		/// </returns>
		public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
		{
			if (destinationType == typeof (Dimension))
				return true;

			return base.CanConvertTo(context, destinationType);
		}

		/// <summary>
		/// Converts the given value object to the specified type, using the specified context and culture information.
		/// </summary>
		/// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param>
		/// <param name="culture">A <see cref="T:System.Globalization.CultureInfo"/>. If null is passed, the current culture is assumed.</param>
		/// <param name="value">The <see cref="T:System.Object"/> to convert.</param>
		/// <param name="destinationType">The <see cref="T:System.Type"/> to convert the <paramref name="value"/> parameter to.</param>
		/// <returns>
		/// An <see cref="T:System.Object"/> that represents the converted value.
		/// </returns>
		/// <exception cref="T:System.ArgumentNullException">The <paramref name="destinationType"/> parameter is null. </exception>
		/// <exception cref="T:System.NotSupportedException">The conversion cannot be performed. </exception>
		public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value,
		                                 Type destinationType)
		{
			if ((destinationType == typeof (String)) && (value is Dimension))
			{
				var dimension = (Dimension) value;

				if (dimension.X.HasValue || dimension.Y.HasValue)
				{
					var stringBuilder = new StringBuilder();

					if (dimension.X.HasValue)
						stringBuilder.Append(dimension.X);

					stringBuilder.Append(';');

					if (dimension.Y.HasValue)
						stringBuilder.Append(dimension.Y);

					return stringBuilder.ToString();
				}

				if (dimension.AbsDimension != null)
					return String.Format(CultureInfo.InvariantCulture, "Abs {0};{1}", dimension.AbsDimension.X, dimension.AbsDimension.Y);

				if (dimension.RelDimension != null)
					return String.Format(CultureInfo.InvariantCulture, "Rel {0};{1}", dimension.RelDimension.X,
					                     dimension.RelDimension.Y);

				return null;
			}

			return base.ConvertTo(context, culture, value, destinationType);
		}

		/// <summary>
		/// Returns whether this converter can convert an object of the given type to the type of this converter, using the specified context.
		/// </summary>
		/// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param>
		/// <param name="sourceType">A <see cref="T:System.Type"/> that represents the type you want to convert from.</param>
		/// <returns>
		/// true if this converter can perform the conversion; otherwise, false.
		/// </returns>
		public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
		{
			if (sourceType == typeof (string))
				return true;

			return base.CanConvertFrom(context, sourceType);
		}

		/// <summary>
		/// Converts the given object to the type of this converter, using the specified context and culture information.
		/// </summary>
		/// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param>
		/// <param name="culture">The <see cref="T:System.Globalization.CultureInfo"/> to use as the current culture.</param>
		/// <param name="value">The <see cref="T:System.Object"/> to convert.</param>
		/// <returns>
		/// An <see cref="T:System.Object"/> that represents the converted value.
		/// </returns>
		/// <exception cref="T:System.NotSupportedException">The conversion cannot be performed. </exception>
		public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
		{
			if (value is string)
			{
				var textValue = (string) value;

				// Remove spaces from text value
				textValue = textValue.Replace(" ", String.Empty);

				if (String.IsNullOrEmpty(textValue))
					return null;

				Match match = dimensionRegex.Match(textValue);

				if (!match.Success)
					throw new ArgumentException("Invalid dimension.");

				if (!match.Groups[1].Success && !match.Groups[4].Success)
					throw new ArgumentException("Invalid dimension.");

				var dimension = new Dimension();

				// optional x attribute
				if (match.Groups[2].Success)
					dimension.X = Int32.Parse(match.Groups[2].Value);

				// optional y attribute
				if (match.Groups[3].Success)
					dimension.Y = Int32.Parse(match.Groups[3].Value);

				// dimension modifier (abs or rel) and both values are specified
				if (match.Groups[5].Success && match.Groups[6].Success && match.Groups[8].Success)
				{
					switch (match.Groups[5].Value)
					{
						case "Abs":
							// only if there are no decimals (absolute coordinates are integers)
							if (match.Groups[7].Success || match.Groups[9].Success)
								throw new ArgumentException("Invalid dimension.");

							dimension.AbsDimension = new AbsDimension
							                         	{
							                         		X = Int32.Parse(match.Groups[6].Value),
							                         		Y = Int32.Parse(match.Groups[8].Value)
							                         	};

							break;
						case "Rel":
							dimension.RelDimension = new RelDimension
							                         	{
							                         		X = Single.Parse(match.Groups[6].Value, CultureInfo.InvariantCulture),
							                         		Y = Single.Parse(match.Groups[8].Value, CultureInfo.InvariantCulture)
							                         	};

							break;
					}
				}

				return dimension;
			}

			return base.ConvertFrom(context, culture, value);
		}
	}
}